import 'dart:async';
import 'dart:ui';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';

// ignore: must_be_immutable
class LoadingAnimations extends StatefulWidget {
  final Color bgColor;
  final Color foregroundColor;
  String? loadingText;
  final double size;
  LoadingAnimations(
      {required this.foregroundColor,
      required this.bgColor,
      this.loadingText,
      this.size = 100.0,
      Key? key})
      : super(key: key);

  @override
  _LoadingAnimationsState createState() => _LoadingAnimationsState();
}

class _LoadingAnimationsState extends State<LoadingAnimations>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;
  late ui.Image planeImage;
  bool isImageLoaded = false;

  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    animation = Tween<double>(begin: 0, end: 1.0).animate(CurvedAnimation(
      parent: controller,
      curve: Curves.linear,
    ))
      ..addListener(() {
        setState(() {});
      });
    controller.repeat();
  }

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: LoadingAnimationPainter(
        animationValue: animation.value,
        bgColor: widget.bgColor,
        foregroundColor: widget.foregroundColor,
        loadingText: widget.loadingText,
        boxSize: widget.size,
      ),
      child: Container(
        width: widget.size,
        height: widget.size,
      ),
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

class LoadingAnimationPainter extends CustomPainter {
  final double animationValue;
  final Color bgColor;
  final Color foregroundColor;
  final String? loadingText;
  final double boxSize;
  LoadingAnimationPainter({
    required this.animationValue,
    required this.bgColor,
    required this.foregroundColor,
    this.loadingText,
    required this.boxSize,
  });

  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = bgColor
      ..strokeWidth = 2.0;

    canvas.drawRRect(
        RRect.fromRectAndRadius(Offset.zero & size, Radius.circular(4.0)),
        paint);

    paint.color = foregroundColor;

    var center = Offset(size.width / 2, size.height / 2);
    if (loadingText != null) {
      _drawText(canvas, loadingText!, size, center);
    }
    _drawBezierLoadingAnimation(canvas, size, center, paint);
  }

  _drawCircleLoadingAnimation(
      Canvas canvas, Size size, Offset center, Paint paint) {
    final radius = boxSize / 2;
    final ballCount = 6;
    final ballRadius = boxSize / 15;

    var circlePath = Path()
      ..addOval(Rect.fromCircle(center: center, radius: radius));

    var circleMetrics = circlePath.computeMetrics();
    for (var pathMetric in circleMetrics) {
      for (var i = 0; i < ballCount; ++i) {
        var lengthRatio = animationValue * (1 - i / ballCount);
        var tangent =
            pathMetric.getTangentForOffset(pathMetric.length * lengthRatio);

        var ballPosition = tangent!.position;
        canvas.drawCircle(ballPosition, ballRadius / (1 + i), paint);
        canvas.drawCircle(
            Offset(size.width - tangent.position.dx,
                size.height - tangent.position.dy),
            ballRadius / (1 + i),
            paint);
      }
    }
  }

  _drawOvalLoadingAnimation(
      Canvas canvas, Size size, Offset center, Paint paint) {
    final ballCount = 6;
    final ballRadius = boxSize / 15;

    var ovalPath = Path()
      ..addOval(Rect.fromCenter(
          center: center, width: boxSize, height: boxSize / 1.5));
    paint.shader = LinearGradient(
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
      colors: [
        this.foregroundColor,
        Colors.blue[400]!,
        Colors.green,
        Colors.yellow,
        Colors.orange,
        this.bgColor
      ],
    ).createShader(Offset.zero & size);
    var ovalMetrics = ovalPath.computeMetrics();
    for (var pathMetric in ovalMetrics) {
      for (var i = 0; i < ballCount; ++i) {
        var lengthRatio = animationValue * (1 - i / ballCount);
        var tangent =
            pathMetric.getTangentForOffset(pathMetric.length * lengthRatio);

        var ballPosition = tangent!.position;
        canvas.drawCircle(ballPosition, ballRadius / (1 + i), paint);
        canvas.drawCircle(
            Offset(size.width - tangent.position.dx,
                size.height - tangent.position.dy),
            ballRadius / (1 + i),
            paint);
      }
    }
  }

  _drawBezierLoadingAnimation(
      Canvas canvas, Size size, Offset center, Paint paint) {
    final ballCount = 30;
    final ballRadius = boxSize / 40;

    var bezierPath = Path()
      ..moveTo(size.width / 2 - boxSize / 2, center.dy)
      ..quadraticBezierTo(size.width / 2 - boxSize / 4, center.dy - boxSize / 4,
          size.width / 2, center.dy)
      ..quadraticBezierTo(size.width / 2 + boxSize / 4, center.dy + boxSize / 4,
          size.width / 2 + boxSize / 2, center.dy)
      ..quadraticBezierTo(size.width / 2 + boxSize / 4, center.dy - boxSize / 4,
          size.width / 2, center.dy)
      ..quadraticBezierTo(size.width / 2 - boxSize / 4, center.dy + boxSize / 4,
          size.width / 2 - boxSize / 2, center.dy);

    var ovalMetrics = bezierPath.computeMetrics();
    for (var pathMetric in ovalMetrics) {
      for (var i = 0; i < ballCount; ++i) {
        var lengthRatio = animationValue * (1 - i / ballCount);
        var tangent =
            pathMetric.getTangentForOffset(pathMetric.length * lengthRatio);

        var ballPosition = tangent!.position;
        canvas.drawCircle(ballPosition, ballRadius / (1 + i), paint);
        canvas.drawCircle(Offset(tangent.position.dy, tangent.position.dx),
            ballRadius / (1 + i), paint);
      }
    }
  }

  void _drawText(Canvas canvas, String text, Size size, Offset center) {
    final fontSize = 16.0;
    final textWidth = boxSize;
    var style = TextStyle(
      fontSize: fontSize,
      color: Colors.black87,
    );

    final ParagraphBuilder paragraphBuilder = ParagraphBuilder(
      ParagraphStyle(
        fontSize: style.fontSize,
        fontFamily: style.fontFamily,
        fontStyle: style.fontStyle,
        fontWeight: style.fontWeight,
        textAlign: TextAlign.center,
      ),
    )
      ..pushStyle(style.getTextStyle())
      ..addText(text);
    final Paragraph paragraph = paragraphBuilder.build()
      ..layout(ParagraphConstraints(width: textWidth));
    canvas.drawParagraph(paragraph,
        Offset(center.dx - textWidth / 2, center.dy - fontSize / 2.0));
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

class LoadingDemo extends StatefulWidget {
  LoadingDemo({Key? key}) : super(key: key);

  @override
  _LoadingDemoState createState() => _LoadingDemoState();
}

class _LoadingDemoState extends State<LoadingDemo> {
  var loaded = false;

  @override
  void initState() {
    super.initState();
    Future.delayed(Duration(seconds: 3), () {
      if (mounted) {
        setState(() {
          loaded = true;
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text('Loading 使用'),
      ),
      body: Center(
        child: loaded
            ? Image.asset(
                'images/beauty.jpeg',
                width: 100.0,
              )
            : LoadingAnimations(
                foregroundColor: Colors.blue,
                bgColor: Colors.white,
                size: 100.0,
              ),
      ),
    );
  }
}
